#include "lexer.hpp"

namespace Lexer
{
    Lexer::Lexer(const std::string& input) : m_input(input), m_position(0), m_readPosition(0) 
    {
        readChar();
    }

    Lexer::~Lexer()
    {
        for (auto token : m_tokens)
        {
            delete token;
        }
    }   
    
    Token::Token* Lexer::nextToken()
    {
        Token::Token* tok;
        skipWhitespace();
        
        switch (m_ch)
        {
        case '=':
            if (peekChar() == '=')
            {
                tok = makeTwoCharToken(Token::EQ);
            }
            else 
            {
                tok = new Token::Token(Token::ASSIGN, std::string(&m_ch));
            }
            break;
        case '+':
            tok = new Token::Token(Token::PLUS, std::string(&m_ch));
            break;
        case '-':
            tok = new Token::Token(Token::MINUS, std::string(&m_ch));
            break;
        case '!':
            if (peekChar() == '=') 
            {
                tok = makeTwoCharToken(Token::NOT_EQ);
            }
            else
            {
                tok = new Token::Token(Token::BANG, std::string(&m_ch));
            }
            break;
        case '/':
            tok = new Token::Token(Token::SLASH, std::string(&m_ch));
            break;
        case '*':
            tok = new Token::Token(Token::ASTERISK, std::string(&m_ch));
            break;
        case '<':
            tok = new Token::Token(Token::LT, std::string(&m_ch));
            break;
        case '>':
            tok = new Token::Token(Token::GT, std::string(&m_ch));
            break;
        case ';':
            tok = new Token::Token(Token::SEMICOLON, std::string(&m_ch));
            break;
        case ',':
            tok = new Token::Token(Token::COMMA, std::string(&m_ch));
            break;
        case '(':
            tok = new Token::Token(Token::LPAREN, std::string(&m_ch));
            break;
        case ')':
            tok = new Token::Token(Token::RPAREN, std::string(&m_ch));
            break;
        case '{':
            tok = new Token::Token(Token::LBRACE, std::string(&m_ch));
            break;
        case '}':
            tok = new Token::Token(Token::RBRACE, std::string(&m_ch));
            break;
        case '[':
            tok = new Token::Token(Token::LBRACKET, std::string(&m_ch));
            break;
        case ']':
            tok = new Token::Token(Token::RBRACKET, std::string(&m_ch));
            break;
        case ':':
            tok = new Token::Token(Token::COLON, std::string(&m_ch));
            break;
        case 0:
            tok = new Token::Token(Token::MEOF, std::string(""));
            break;
        default:
            if (isalpha(m_ch))
            {
                std::string literal = readIdentifier();
                Token::TokenType type = Token::LookUpIdent(literal);
                tok = new Token::Token(type, literal);
                return tok;
            }
            else if (isdigit(m_ch))
            {
                std::string literal = readNumber();
                Token::TokenType type = Token::CINT;
                tok = new Token::Token(type, literal);
                return tok;
            }
            else
            {
                tok = new Token::Token(Token::ILLEGAL, std::string(&m_ch));
            }
        }
        readChar();
        return tok;
    }

    void Lexer::Run()
    {
        Token::Token* token = nextToken();
        while (token && token->m_type != Token::MEOF)
        {
            m_tokens.push_back(token);
            token = nextToken();
        }
    }

    std::vector<Token::Token*> Lexer::GetTokens()
    {
        return m_tokens;
    }
    
    Token::Token* Lexer::makeTwoCharToken(Token::TokenType tokenType)
    {
        char ch = m_ch;
        readChar();
        std::string literal = std::string(&ch) + std::string(&m_ch);
        Token::Token* token = new Token::Token(tokenType, literal);
        return token;
    }

    void Lexer::readChar()
    {
        if (m_readPosition >= m_input.size())
        {
            m_ch = 0;
        }
        else
        {
            m_ch = m_input[m_readPosition];
        }
        m_position = m_readPosition;
        m_readPosition += 1;
    }

    std::string Lexer::readString()
    {
        int position = m_position + 1;
        while(m_ch != '"' && m_ch != 0)
        {
            readChar();
        }
        return m_input.substr(position, m_position - position);
    }

    std::string Lexer::readIdentifier()
    {
        int position = m_position;
        while(isalpha(m_ch))
        {
            readChar();
        }
        return m_input.substr(position, m_position - position);
    }

    std::string Lexer::readNumber()
    {
        int position = m_position;
        while (isdigit(m_ch))
        {
            readChar();
        }
        return m_input.substr(position, m_position - position);
    }

    void Lexer::skipWhitespace()
    {
        while (m_ch == ' ' || m_ch == '\t' || m_ch == '\n' || m_ch == '\r')
        {
            readChar();
        }
    }

    char Lexer::peekChar()
    {
        if (m_readPosition >= m_input.size())
        {
            return 0;
        }
        else
        {
            return m_input[m_readPosition];
        }
    }
}